package cn.botter.subject.infra.basic.es;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.http.HttpHost;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchType;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.reindex.BulkByScrollResponse;
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
import org.elasticsearch.search.Scroll;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.ScoreSortBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.util.*;

/**
 * @BelongsProject: bo-club
 * @BelongsPackage: cn.botter.subject.infra.basic.es
 * @Author: 爱写Bug的Botter
 * @CreateTime: 2025-04-03:21:02
 * @Description: 这是一个对elasticsearch各种请求的封装类
 * @Version: 1.0
 */
@Component
@Slf4j
public class EsRestClient {
    /**
     * 定义一个全局的高级客户端Map
     */
    private static Map<String, RestHighLevelClient> clientMap = new HashMap<>();
    /**
     * 将Es的配置加载到properties
     */
    @Autowired
    private EsClusterProperties properties;
    private static final RequestOptions COMMON_OPTIONS;

    static {
        COMMON_OPTIONS = RequestOptions.DEFAULT.toBuilder().build();
    }

    @PostConstruct
    public void initialize() {
        List<EsClusterConfig> esConfigs = properties.getEsConfigs();
        for (EsClusterConfig config : esConfigs) {
            RestHighLevelClient restHighLevelClient = initRestHighClient(config);
            if (restHighLevelClient != null) {
                clientMap.put(config.getName(), restHighLevelClient);
                log.info("initialize.config.name:{},node:{} was Successfully!", config.getName(), config.getNodes());
            } else {
                log.info("initRestError:{},please check whit these ip or port are right .\ntemplates:\"ip1:port,ip2:port\"", config);
            }
        }
    }

    /**
     * 初始化一个集群
     *
     * @param esClusterConfig
     * @return
     */
    private RestHighLevelClient initRestHighClient(EsClusterConfig esClusterConfig) {
        String[] ipPortArr = esClusterConfig.getNodes().split(",");//用逗号分割集群节点
        List<HttpHost> httpHostList = new ArrayList<>(ipPortArr.length);
        for (String ipPort : ipPortArr) {
            //分割ipaddress   "IP:PORT"    =》  ["IP","PORT"]
            String[] ipPortInfo = ipPort.split(":");
            if (ipPortInfo.length == 2) {
                //新建一个HeepHost类
                HttpHost httpHost = new HttpHost(ipPortInfo[0], NumberUtils.toInt(ipPortInfo[1]));
                httpHostList.add(httpHost);
            } else {
                log.error("Invalid ip port");
                new RuntimeException("Invalid ip port");
                return null;
            }
        }

        HttpHost[] httpHosts = new HttpHost[httpHostList.size()];
        httpHostList.toArray(httpHosts);
        RestClientBuilder builder = RestClient.builder(httpHosts);
        return new RestHighLevelClient(builder);
    }

    private static RestHighLevelClient getClient(String clusterName) {
        RestHighLevelClient restHighLevelClient = clientMap.get(clusterName);
        return restHighLevelClient;
    }

    /**
     * 新增文档
     *
     * @param esIndexInfo
     * @param esSourceData
     * @return
     */
    public static boolean insertDoc(EsIndexInfo esIndexInfo, EsSourceData esSourceData) {
        try {
            //根据索引名创建一个索引请求类
            IndexRequest indexRequest = new IndexRequest(esIndexInfo.getIndexName());
            indexRequest.source(esSourceData.getData()); //将要存放的数据
            indexRequest.id(esSourceData.getDocId());  //将要请求的文档id放入
            IndexResponse index = getClient(esIndexInfo.getClusterName()).index(indexRequest, COMMON_OPTIONS);
            if (log.isInfoEnabled()) {
                log.info("insertDoc.ok:{},info{}", esIndexInfo, index);
            }
            return true;
        } catch (Exception e) {
            log.error("insertDoc.error{},{}", e.getMessage(), e, e);
        }
        return false;
    }

    /**
     * 修改文档
     */
    public static boolean updateDoc(EsIndexInfo esIndexInfo, EsSourceData esSourceData) {
        try {
            //根据索引名创建一个索引请求类
            UpdateRequest updateRequest = new UpdateRequest();
            updateRequest.index(esIndexInfo.getIndexName());
            updateRequest.doc(esSourceData.getData());
            updateRequest.id(esSourceData.getDocId());  //将要请求的文档id放入
            UpdateResponse update = getClient(esIndexInfo.getClusterName()).update(updateRequest, COMMON_OPTIONS);
            if (log.isInfoEnabled()) {
                log.info("insertDoc.ok:{},info{}", esIndexInfo, update);
            }
            return true;
        } catch (Exception e) {
            log.error("insertDoc.error{},{}", e.getMessage(), e, e);
        }
        return false;
    }

    /**
     * 这是一个批量更新文档的方法
     *
     * @param esIndexInfo
     * @param esSourceDataList
     * @return
     */
    public static boolean batchUpdateDoc(EsIndexInfo esIndexInfo, List<EsSourceData> esSourceDataList) {
        try {
            boolean flag = false;
            BulkRequest bulkRequest = new BulkRequest();
            for (EsSourceData esSourceData : esSourceDataList) {
                String docId = esSourceData.getDocId();
                if (StringUtils.isNotBlank(docId)) {
                    UpdateRequest updateRequest = new UpdateRequest();
                    updateRequest.index(esIndexInfo.getIndexName());
                    updateRequest.id(esSourceData.getDocId());
                    updateRequest.doc(esSourceData.getData());
                    bulkRequest.add(updateRequest);
                    flag = true;
                }

            }
            if (flag) {
                BulkResponse bulk = getClient(esIndexInfo.getClusterName()).bulk(bulkRequest, COMMON_OPTIONS);
                if (bulk.hasFailures()) {
                    return false;
                }
            }
            //这里更多的是关注成功的业务，所以日志就不写了
            return true;
        } catch (Exception e) {
            log.error("insertDoc.error{},{}", e.getMessage(), e, e);
        }
        return false;
    }

    /**
     *按条件删除
     */

    public static boolean delete(EsIndexInfo esIndexInfo){
        try{
            DeleteByQueryRequest deleteByQueryRequest = new DeleteByQueryRequest(esIndexInfo.getIndexName());
            deleteByQueryRequest.setQuery(QueryBuilders.matchAllQuery());
            BulkByScrollResponse response = getClient(esIndexInfo.getClusterName()).deleteByQuery(deleteByQueryRequest, COMMON_OPTIONS);
            long deleted = response.getDeleted();
            if (log.isInfoEnabled()) {
                log.info("delete.ok.deletedSize:{}",deleted);
            }
            return true;

        }catch (Exception e){
            if (log.isErrorEnabled()) {
                log.error("deleteDoc.error{},{}", e.getMessage(), e, e);
            }
        }
        return false;
    }


    /**
     * 删除文档信息
     */
    public static boolean deleteDoc(EsIndexInfo esIndexInfo , String docId){

        try{
            DeleteRequest deleteRequest = new DeleteRequest(esIndexInfo.getClusterName());
            deleteRequest.id(docId);
            DeleteResponse delete = getClient(esIndexInfo.getClusterName()).delete(deleteRequest, COMMON_OPTIONS);
            if (log.isInfoEnabled()) {
                log.info("deleteDoc.response:{}",delete);
            }
            return true;
        }catch (Exception e){
            if (log.isErrorEnabled()) {
                log.error("deleteDoc.error{},{}", e.getMessage(), e, e);
            }
        }
        return false;
    }

    /**
     * 根据文档id查看文档是否存在
     */
    public static boolean isExistDocById(EsIndexInfo esIndexInfo, String docId){
        GetRequest getRequest = new GetRequest(esIndexInfo.getIndexName());
        getRequest.id(docId);
        try {
            return getClient(esIndexInfo.getClusterName()).exists(getRequest , COMMON_OPTIONS);
        } catch (IOException e) {
            if (log.isErrorEnabled()) {
                log.error("isExistDocById.error{},{}", e.getMessage(), e, e);
            }
            throw new RuntimeException(e);
        }
    }

    /**
     * 根据文档id，查看命中的值
     */
    public static Map<String , Object> getDocById(EsIndexInfo esIndexInfo, String docId){
        GetRequest getRequest = new GetRequest(esIndexInfo.getIndexName());
        getRequest.id(docId);
        try {
            GetResponse documentFields = getClient(esIndexInfo.getClusterName()).get(getRequest , COMMON_OPTIONS);
            Map<String, Object> source = documentFields.getSource();
            return source;
        } catch (IOException e) {
            log.error("getDocById.error{},{}", e.getMessage(), e, e);
        }
        return null;
    }

    /**
     *  根据过滤的fields参数进行过滤
     */
    public static Map<String, Object> getDocById(EsIndexInfo esIndexInfo, String docId,
                                                 String[] fields) {
        try {
            GetRequest getRequest = new GetRequest(esIndexInfo.getIndexName());
            getRequest.id(docId);
            FetchSourceContext fetchSourceContext = new FetchSourceContext(true, fields, null);
            getRequest.fetchSourceContext(fetchSourceContext);
            GetResponse response = getClient(esIndexInfo.getClusterName()).get(getRequest, COMMON_OPTIONS);
            Map<String, Object> source = response.getSource();
            return source;
        } catch (Exception e) {
            log.error("isExistDocById.exception:{}", e.getMessage(), e);
        }
        return null;
    }

    public static SearchResponse searchWithTermQuery(EsIndexInfo esIndexInfo, EsSearchRequest esSearchRequest){
        try {
            BoolQueryBuilder bq = esSearchRequest.getBq();
            String[] fields = esSearchRequest.getFields();
            int from = esSearchRequest.getFrom();
            int size = esSearchRequest.getSize();
            //快照存活时间
            Long minutes = esSearchRequest.getMinutes();
            //是否为快照
            Boolean needScroll = esSearchRequest.getNeedScroll();
            String sortName = esSearchRequest.getSortName(); // 排序名称
            SortOrder sortOrder = esSearchRequest.getSortOrder(); //排序选项
            HighlightBuilder highlight = esSearchRequest.getHighlight();
            SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
            searchSourceBuilder.query(bq);
            searchSourceBuilder.fetchSource(fields , null).from(from).size(size);
            if (Objects.nonNull(highlight)) {
                searchSourceBuilder.highlighter(highlight);
            }
            if (StringUtils.isNotBlank(sortName)) {
                searchSourceBuilder.sort(sortName);
            }
            searchSourceBuilder.sort(new ScoreSortBuilder().order(SortOrder.DESC));

            SearchRequest searchRequest = new SearchRequest();
            searchRequest.searchType(SearchType.DEFAULT);
            searchRequest.indices(esIndexInfo.getIndexName());
            searchRequest.source(searchSourceBuilder);
            if (needScroll) {
                Scroll scroll = new Scroll(TimeValue.timeValueMinutes(minutes));
                searchRequest.scroll(scroll);
            }
            SearchResponse search = getClient(esIndexInfo.getClusterName()).search(searchRequest, COMMON_OPTIONS);
            return search;
        }catch (Exception e){
            if (log.isErrorEnabled()) {
                log.error("searchWithTermQuery.error{},{}", e.getMessage(), e, e);
            }
        }
        return null;
    }
}
